﻿using OA.Infrastructure.Extensions;

namespace AMS.Services.Impl;

public class PurchaseService : IPurchaseService
{
    private readonly DbContext _context;

    private readonly IApprovalService _approvalService;

    public PurchaseService(DbContext context, IApprovalService approvalService)
    {
        _context = context;

        _approvalService = approvalService;
    }

    public async Task<PageResult<Purchase.QueryModel>> GetPageAsync(Purchase.ConditionSelectorCondition condition, CancellationToken cancellationToken)
    {
        var result = new PageResult<Purchase.QueryModel>();

        var currentUser = _context.CurrentUser();

        var accessExpression = currentUser.Roles.Any(n => n.Name == "admin") ? (n => true) : (Expression<Func<Purchase, bool>>?)(n => n.Commiter.Id == currentUser.Id);

        try
        {
            var expression = GetConditionSelectorCondition(condition);

            var data = await _context.Set<Purchase>()
                .Include(m => m.PurchaseDetails).ThenInclude(x => x.Supplier)
                .Include(m => m.Commiter)
                .Where(accessExpression)
                .Where(expression)
                .OrderByDescending(x => x.CreatedDate)
                .Skip((condition.PageNum - 1) * condition.PageSize)
                .Take(condition.PageSize)
                .AsNoTracking()
                .ToListAsync(cancellationToken);

            result.Data = data.Select(x => GetQueryModel(x));

            result.PageNum = condition.PageNum;

            result.PageSize = condition.PageSize;

            result.Total = await _context.Set<Purchase>().Where(accessExpression).CountAsync(expression, cancellationToken);

            result.Status = OperationalResult.SUCCESS;
        }
        catch (Exception error)
        {
            result.Status = OperationalResult.ERROR;

            result.Message = error.ToString();
        }

        return result;
    }

    public async Task<OperationalResult> CreateAsync(Purchase.CreateModel purchase, CancellationToken cancellationToken)
    {
        var result = new OperationalResult();

        var committer = _context.CurrentUser();

        try
        {
            var entity = new Purchase
            {
                Remark = purchase.Remark,
                Commiter = committer
            };

            foreach (var purchaseDetailItem in purchase.PurchaseDetails)
            {
                var purchaseDetail = new PurchaseDetail
                {
                    Name = purchaseDetailItem.Name,
                    Count = purchaseDetailItem.Count,
                    Univalent = purchaseDetailItem.Univalent,
                };

                var purchaseDetailSupplier = await _context.Set<Supplier>().FindAsync(new object[] { purchaseDetailItem.SupplierId }, cancellationToken);

                if (purchaseDetailSupplier is not null)
                {
                    purchaseDetail.Supplier = purchaseDetailSupplier;
                }

                entity.PurchaseDetails.Add(purchaseDetail);
            }

            await _context.Set<Purchase>().AddAsync(entity, cancellationToken);

            var affectedRows = await _context.SaveChangesAsync(cancellationToken);

            result.Status = affectedRows > 0 ? OperationalResult.SUCCESS : OperationalResult.FAILURE;

            result.Message = $"{affectedRows} rows affected.";

            _ = await _approvalService.CreateApprovalAsync(committer, entity.Id, ApprovalType.Requisition, AssetsType.Consumable, purchase.Remark, ApprovalService.purchaseApprovalFlow, cancellationToken);
        }
        catch (Exception error)
        {
            result.Status = OperationalResult.ERROR;

            result.Message = error.ToString();
        }

        return result;
    }

    private static Purchase.QueryModel GetQueryModel(Purchase purchase)
    {
        return new Purchase.QueryModel
        {
            Id = purchase.Id,
            CreatedDate = purchase.CreatedDate,
            LastModifiedDate = purchase.LastModifiedDate,
            PurchaseDetails = purchase.PurchaseDetails.Select(x => PurchaseDetailService.GetQueryModel(x)),
            Remark = purchase.Remark,
            Commiter = DefinitionServiceExtensions.GetUserQueryModel(purchase.Commiter),
            Status = purchase.Status,
        };
    }

    private static Expression<Func<Purchase, bool>> GetConditionSelectorCondition(Purchase.ConditionSelectorCondition condition)
    {
        Expression<Func<Purchase, bool>> expression = x => true;

        if (condition.PurchaseStatus is not null)
        {
            Expression<Func<Purchase, bool>>? purchaseStatus = null;
            purchaseStatus = x => condition.PurchaseStatus == x.Status;
            expression = expression.And(purchaseStatus);
        }

        var createDateSelectorCondition = new Purchase.CreateDateSelectorCondition()
        {
            BeginDate = condition.CreateDateSelectorBeginDate,
            EndDate = condition.CreateDateSelectorEndDate,
        };

        expression = expression.And(GetCreateDateSelectorCondition(createDateSelectorCondition));

        return expression;
    }

    private static Expression<Func<Purchase, bool>> GetCreateDateSelectorCondition(Purchase.CreateDateSelectorCondition condition)
    {
        Expression<Func<Purchase, bool>> expression = x => true;

        if (condition.BeginDate is not null)
        {
            Expression<Func<Purchase, bool>>? beginDateSelector = null;
            beginDateSelector = x => x.CreatedDate >= condition.BeginDate.Value;
            expression = expression.And(beginDateSelector);
        }

        if (condition.EndDate is not null)
        {
            Expression<Func<Purchase, bool>>? endDateSelector = null;
            endDateSelector = x => x.CreatedDate <= condition.EndDate.Value;
            expression = expression.And(endDateSelector);
        }

        return expression;
    }
}